﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Commands.Settings;
using Uploaders;

namespace Commands.TorrentDownloaders
{
    internal abstract class TokenDispatcherBase: ITokenDispatcher
    {
        #region public methods

        public TokenDispatcherBase(ServiceSettings settings)
        {
            Settings = settings;
            _InitWorker();
        }

        /// <summary>
        /// Check that dispatcher can download torrent file from this token. 
        /// Add token to download list if it can.
        /// </summary>
        /// <param name="token">Token to check.</param>
        /// <param name="dirParam">Folder to download torrent content.</param>
        public bool CheckTokenCanBeDownloaded(string token, string dirParam)
        {
            string url = token.ToString();

            Regex regex = new Regex(CHECK_TRACKER_REGEXP, RegexOptions.IgnoreCase);

            if (!regex.Match(url).Success)
                return false;

            if (!_worker.IsBusy)
                _worker.RunWorkerAsync();

            // If we haven't downloaded url before - add it to download list.
            if (!urls.ContainsKey(url))
            {
                urls[url] = dirParam;
            }

            return true;
        }

        #endregion

        #region Protected members

        /// <summary>
        /// Decode response from bytes to string.
        /// Decode bytes by UTF-8 encoding.
        /// </summary>
        protected virtual string DecodeResponseBytes(byte[] bytesLoginResponse)
        {
            return Encoding.UTF8.GetString(bytesLoginResponse);
        }

        /// <summary>
        /// Add torrent to torrent manager function.
        /// </summary>
        protected void StartTorrent(string torentFilePath, string folder)
        {
            TorrentManager.StartTorrent(torentFilePath, folder);
        }

        /// <summary>
        /// Collection of values which must be uploaded to log in.
        /// </summary>
        protected NameValueCollection GetLoginPasswordData()
        {
            return new NameValueCollection
                {
                    { "login_username", Settings.Login},
                    { "login_password", Settings.Password},
                    { "login", "%C2%F5%EE%E4" }
                };
        }

        /// <summary>
        /// String which indicates that login was successful.
        /// </summary>
        /// TODO do not work on free-torrents.org
        protected string LoginIndicator()
        {
            return String.Format("<b class=\"med\">{0}</b>", Settings.Login).ToLower();
        }

        /// <summary>
        /// Service settings.
        /// </summary>
        protected ServiceSettings Settings;

        #endregion

        #region protected Abstract members

        /// <summary>
        /// Regexp to get torrent download link from response.
        /// </summary>
        protected abstract string DOWNLOAD_LINK_REGEXP { get; }

        /// <summary>
        /// Regexp to check that dispatcher can this link.
        /// </summary>
        protected abstract string CHECK_TRACKER_REGEXP { get; }

        protected abstract Uri DispatcherLoginPage { get; }

        // dictionary:
        // url | flag
        // where:
        // url  - url to be loaded
        // flag - folder to download torrent content or null if torrent has been downloaded.
        protected Dictionary<string, string> urls = new Dictionary<string, string>();

        /// <summary>
        /// Client for communicating with tracker.
        /// </summary>
        protected WebClientExtended client = null;

        #endregion

        #region Private members

        /// <summary>
        /// Init background worker.
        /// </summary>
        private void _InitWorker()
        {
            _worker = new BackgroundWorker();
            _worker.DoWork += (s, e) => { _Work(); };
        }

        /// <summary>
        /// Log-in to tracker, get .torrent file and put it to torrent manager.
        /// </summary>
        private void _Work()
        {
            _ExecuteFunction(_TryLogin, 3, 30);

            while (urls.Any(x => x.Value != null))
            {
                // "ToList()" is to avoid "Collection was modified; 
                // enumeration operation may not execute." exception
                foreach (string url in urls.Keys.ToList())
                {
                    if (urls[url] != null)
                    {
                        // If we can't download a file, try to relogin
                        if (!_ExecuteFunction(() => _TryGetTorrent(url, urls[url]), 3, 10))
                            _ExecuteFunction(_TryLogin, 3, 30);

                        urls[url] = null;
                    }
                }

                Thread.Sleep(WORKER_SLEEP_TIME_MILLISEC);
            }
        }

        /// <summary>
        /// Function for login to tracker.
        /// </summary>
        private bool _TryLogin()
        {
            client = new WebClientExtended();
            var data = GetLoginPasswordData();
            var response = client.UploadValues(DispatcherLoginPage, data);
            string str = Encoding.UTF8.GetString(response).ToLower();

            // Check that we have logged in.
            // TESTCODE
            //return str.Contains(LoginIndicator());
            return true;
        }

        /// <summary>
        /// Try to download torrent from url.
        /// </summary>
        private bool _TryGetTorrent(string url, string folder)
        {
            var success = false;

            // Get response string.
            var bytesLoginResponse = client.DownloadData(url);
            var responseString = DecodeResponseBytes(bytesLoginResponse);

            // Try find download link.
            Regex regexSuccessfullLogin = new Regex(DOWNLOAD_LINK_REGEXP);
            Match match = regexSuccessfullLogin.Match(responseString);
            if (match.Success)
            {
                string downloadLink = match.Groups["download_link"].Value;
                downloadLink = FormatDownloadLink(downloadLink);

                // If link has been found - download torrent file.
                string filename = _DownloadToFile(downloadLink, url);

                StartTorrent(filename, folder);
                success = true;
            }

            return success;
        }

        protected virtual string FormatDownloadLink(string downloadLink)
        {
            return downloadLink;
        }

        /// <summary>
        /// Execute function in case if some exception occured or function returns false, 
        /// function will be executed again but no more times then maxTries.
        /// </summary>
        /// <param name="function">Function to execute.</param>
        /// <param name="maxTries">Number of tries.</param>
        /// <param name="timeoutSec">Timeout to wait before next try.</param>
        /// <returns>Lust function result.</returns>
        private bool _ExecuteFunction(Func<bool> function, int maxTries, int timeoutSec)
        {
            if (function == null)
                throw new ArgumentNullException("action");

            bool success = false;
            do
            {
                try 
                { 
                    success = function(); 
                }
                catch(WebException exception)
                {
                }

                if(!success)
                    Thread.Sleep(timeoutSec * 1000);
            } 
            while (maxTries-- > 0 && !success);

            return success;
        }

        /// <summary>
        /// Download from link to file.
        /// </summary>
        /// <param name="link">URL to download.</param>
        /// <param name="referer">Referer to specify when downloading.
        /// Usually a tracker page.</param>
        /// <returns></returns>
        private string _DownloadToFile(string link, string referer)
        {
            client.Headers.Add("Referer", referer);
            var response = client.UploadValues(link, new NameValueCollection { });
            client.Headers.Remove("Referer");

            string path = _GetPath();
            File.WriteAllBytes(path, response);
            return path;
        }

        private string _GetPath()
        {
            DateTime now = DateTime.Now;
            string filename = String.Format("{0:D4}-{1:D2}-{2:D2}_{3:D2}-{4:D2}-{5:D2}_{6}.torrent",
                    now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Millisecond);

            return FilesHelper.GetTempFilePath(filename);
        }

        #endregion

        #region Constants

        protected const int ONE_MINUTE = 60 * 1000;

        private const int WORKER_SLEEP_TIME_MILLISEC = 5 * ONE_MINUTE;

        /// <summary>
        /// Background worker responsible for work with tracker server.
        /// </summary>
        private BackgroundWorker _worker;

        #endregion
    }
}
